package cn.xinfei.xdecision.data.core.util;

import cn.xinfei.xdecision.common.model.datax.datamanage.v2.Variable;
import cn.xinfei.xdecision.common.model.datax.enums.EnumUtil;
import cn.xinfei.xdecision.common.model.datax.enums.VariableTypeEnum;
import cn.xinfei.xdecision.common.model.datax.exception.XDecisionDataException;
import cn.xinfei.xdecision.common.model.datax.exception.XDecisionDataExceptionType;

import cn.xinfei.xdecision.data.core.frame.executors.groovy.GroovySupport;
import cn.xinfei.xdecision.data.core.vo.groovy.*;
import cn.xinfei.xdecision.data.core.vo.groovy.rule.*;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import groovy.lang.Binding;
import groovy.lang.MissingPropertyException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.codehaus.groovy.control.MultipleCompilationErrorsException;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;


@Slf4j
@Service
public class ExpressionUtil {

    @Resource
    private GroovySupport exprSupport;


    private static List<String> BASIC_DATA_TYPES = Arrays.asList("integer", "double", "boolean", "string");

    public void ruleExpressionTest(MixExpression mixExpression) {
        testExpression(mixExpression.getExpression(), mixExpression.getVariableList(), "boolean");

    }

    /**
     * 用变量默认值校验表达式
     *
     * @param mixExpressionStr 表达式
     * @param variableList     变量列表
     * @param resultType       表达式结果类型
     * @return MixExpressionCheckResult
     */
    public void testExpression(String mixExpressionStr, List<Variable> variableList, String resultType) {

        MixExpressionTestData mixExpressionTestData = new MixExpressionTestData();
        mixExpressionTestData.setRowId(0);
        for (Variable variable : variableList) {
            VarTestData testData = new VarTestData();
            testData.setVarCode(variable.getVarCode());
            testData.setValueType(String.valueOf(variable.getVarType()));
            testData.setVarValue(variable.getVarDefaultValue());
            mixExpressionTestData.getTestDatas().add(testData);
        }

        MixExpressionCheck mixExpressionCheck = new MixExpressionCheck();
        mixExpressionCheck.setExpression(mixExpressionStr);
        mixExpressionCheck.setExpressionResultType(resultType);
        mixExpressionCheck.getMixExpressionTestDataList().add(mixExpressionTestData);
        MixExpressionCheckResult checkResult = testExpression(mixExpressionCheck).get(0);
        if ("fail".equals(checkResult.getTestResult())) {
            throw new XDecisionDataException(XDecisionDataExceptionType.VARIABLE_TYPE_ERROR,
                    checkResult.getMessage());
        }
    }

    public List<MixExpressionCheckResult> testExpression(MixExpressionCheck mixExpressionCheck) {
        String expression = mixExpressionCheck.getExpression();
        String expressionResultType = mixExpressionCheck.getExpressionResultType();
        log.info("表达式校验开始，id:{},expression:{},expressionResultType:{}", mixExpressionCheck.getId(), expression, expressionResultType);
        List<JSONObject> bindings = scriptBinding(mixExpressionCheck.getMixExpressionTestDataList());

        if (bindings.isEmpty()) {
            log.error("表达式绑定数据为空！");
            throw new XDecisionDataException(XDecisionDataExceptionType.EXPRESSION_ERROR,
                    "表达式绑定数据为空！");
        }

        List<MixExpressionCheckResult> checkResults = new ArrayList<>();
        for (JSONObject binding : bindings) {
            Binding binding1 = (Binding) binding.get("binding");
            Integer rowId = binding.getInteger("row_id");
            String desiredResult = binding.getString("desired_result");

            MixExpressionCheckResult checkResult = null;
            Object result;

            try {
//                ScriptTemplate.scriptExecutionTest(expression,binding1)
                result = exprSupport.parseExprWithAt(expression, binding1);

                if (StringUtils.isNotEmpty(expressionResultType) && !resultDataTypeCheck(expressionResultType, result)) {
                    //测试不通过，表达式类型与结果类型不符
                    checkResult = new MixExpressionCheckResult(rowId,
                            "fail", String.format("表达式结果类型校验失败。期望的类型：%s，实际的计算值：%s",
                            expressionResultType, result));
                }

                if (StringUtils.isNotEmpty(desiredResult) && !resultDataCheck(expressionResultType, desiredResult, result)) {
                    //测试不通过，与期望结果不符
                    checkResult = new MixExpressionCheckResult(rowId,
                            "fail", String.format("与期望结果不符。期望的计算结果：%s，实际的计算值：%s",
                            desiredResult, result));
                }

                if (checkResult == null) {
                    checkResult = new MixExpressionCheckResult(rowId, "success", "测试通过");
                }
                checkResults.add(checkResult);
            } catch (MissingPropertyException e1) {
                log.error("", e1);
                checkResult = new MixExpressionCheckResult(rowId, "fail", "解析失败，表达式里存在未选择变量");
                checkResults.add(checkResult);
            } catch (MultipleCompilationErrorsException e2) {
                log.error("", e2);
                checkResult = new MixExpressionCheckResult(rowId, "fail", "解析失败，符号使用错误");
                checkResults.add(checkResult);
            } catch (Exception e3) {
                log.error("", e3);
                checkResult = new MixExpressionCheckResult(rowId, "fail", "解析失败");
                checkResults.add(checkResult);
            }
        }
        log.info("表达式校验结束");
        return checkResults;
    }

//
//    public List<RuleMixExpressionCheckResult> testScriptRule(RuleMixExpressionCheck mixExpressionCheck) {
//        String expression = mixExpressionCheck.getExpression();
//        String expressionResultType = mixExpressionCheck.getExpressionResultType();
//        log.info("表达式校验开始，id:{},expression:{},expressionResultType:{}", mixExpressionCheck.getId(), expression, expressionResultType);
//        List<JSONObject> bindings = scriptRuleBinding(mixExpressionCheck.getMixExpressionTestDataList());
//
//        if (bindings.isEmpty()) {
//            log.error("表达式绑定数据为空！");
//            throw new XDecisionDataException(XDecisionDataExceptionType.EXPRESSION_ERROR,
//                    "表达式绑定数据为空！");
//        }
//
//        List<RuleMixExpressionCheckResult> checkResults = new ArrayList<>();
//        for (JSONObject binding : bindings) {
//            Map<String, RuleTestData> ruleTestDataMap = (Map<String, RuleTestData>) binding.get("binding");
//            Integer rowId = binding.getInteger("row_id");
//            String desiredResult = binding.getString("desired_result");
//            RuleMixExpressionCheckResult checkResult = null;
//            Object result;
//            RuleScriptVO ruleScriptVO;
//            try {
//                result = ScriptTemplate.scriptExecution(expression, ruleTestDataMap);
//                ruleScriptVO = JSON.parseObject(JsonUtil.toJson(result), RuleScriptVO.class);
//                if (StringUtils.isNotEmpty(desiredResult) && !resultDataCheck(expressionResultType, desiredResult, ruleScriptVO.getHitResult())) {
//                    //测试不通过，与期望结果不符
//                    checkResult = new RuleMixExpressionCheckResult(
//                            rowId,
//                            "fail",
//                            0,
//                            new HashMap<>(),
//                            String.format("与期望结果不符。期望的计算结果：%s，实际的计算值：%s",
//                                    desiredResult, JsonUtil.toJson(ruleScriptVO.getHitResult())));
//                }
//
//                if (checkResult == null) {
//                    checkResult = new RuleMixExpressionCheckResult(
//                            rowId,
//                            ruleScriptVO.getHitResult(),
//                            ruleScriptVO.getRuleScore(),
//                            ruleScriptVO.getContext(),
//                            "测试通过");
//                }
//                checkResults.add(checkResult);
//            } catch (MissingPropertyException e1) {
//                log.error("", e1);
//                checkResult = new RuleMixExpressionCheckResult(
//                        rowId,
//                        "fail",
//                        0,
//                        new HashMap<>(),
//                        "解析失败，表达式里存在未选择变量");
//                checkResults.add(checkResult);
//            } catch (MultipleCompilationErrorsException e2) {
//                log.error("", e2);
//                checkResult = new RuleMixExpressionCheckResult(
//                        rowId,
//                        "fail",
//                        0,
//                        new HashMap<>(),
//                        "解析失败，符号使用错误");
//                checkResults.add(checkResult);
//            } catch (Exception e3) {
//                log.error("", e3);
//                checkResult = new RuleMixExpressionCheckResult(
//                        rowId,
//                        "fail",
//                        0,
//                        new HashMap<>(),
//                        "解析失败");
//                checkResults.add(checkResult);
//            }
//        }
//        log.info("表达式校验结束");
//        return checkResults;
//    }


    /**
     * 表达式测试数据绑定
     *
     * @param testDatas 测试数据集
     * @return key:测试数据行号，value:Binding
     */
    private List<JSONObject> scriptBinding(List<MixExpressionTestData> testDatas) {
        List<JSONObject> bindings = new ArrayList<>();
        if (testDatas == null || testDatas.isEmpty()) {
            return bindings;
        }

        for (MixExpressionTestData testData : testDatas) {
            JSONObject bindingObj = new JSONObject();
            Binding binding = new Binding();
            //一组测试数据
            List<VarTestData> datas = testData.getTestDatas();
            if (datas == null || datas.isEmpty()) {
                break;
            }
            for (VarTestData data : datas) {
                VariableTypeEnum variableTypeEnum = EnumUtil.fromNameOrThrow(VariableTypeEnum.class, data.getValueType());
                if (BASIC_DATA_TYPES.contains(variableTypeEnum.name().toLowerCase())) {
                    switch (EnumUtil.fromNameOrThrow(VariableTypeEnum.class, data.getValueType())) {
                        case INTEGER:
                            binding.setVariable(data.getVarCode(), Integer.valueOf(data.getVarValue()));
                            break;
                        case DOUBLE:
                            binding.setVariable(data.getVarCode(), Double.valueOf(data.getVarValue()));
                            break;
                        case STRING:
                            binding.setVariable(data.getVarCode(), data.getVarValue());
                            break;
                        case BOOLEAN:
                            binding.setVariable(data.getVarCode(), Boolean.valueOf(data.getVarValue()));

                    }
                } else {
                    binding.setVariable(data.getVarCode(), null);
                }
            }
            bindingObj.put("row_id", testData.getRowId());
            bindingObj.put("binding", binding);
            bindingObj.put("desired_result", testData.getDesiredResult());
            bindings.add(bindingObj);
        }
        return bindings;
    }


    /**
     * 表达式测试数据绑定
     *
     * @param testDatas 测试数据集
     * @return key:测试数据行号，value:Binding
     */
    private List<JSONObject> scriptRuleBinding(List<RuleMixExpressionTestData> testDatas) {
        List<JSONObject> bindings = new ArrayList<>();
        if (testDatas == null || testDatas.isEmpty()) {
            return bindings;
        }

        for (RuleMixExpressionTestData testData : testDatas) {
            JSONObject bindingObj = new JSONObject();
            //一组测试数据
            List<RuleTestData> datas = testData.getTestDatas();
            if (datas == null || datas.isEmpty()) {
                break;
            }
            Map<String, RuleTestData> testDataMap = datas.stream()
                    .filter(Objects::nonNull)
                    .collect(Collectors.toMap(RuleTestData::getVarCode, Function.identity()));
            bindingObj.put("row_id", testData.getRowId());
            bindingObj.put("binding", testDataMap);
            bindingObj.put("desired_result", testData.getDesiredResult());
            bindings.add(bindingObj);
        }
        return bindings;
    }


    /**
     * 数据类型校验
     *
     * @param desiredType 支持："integer","double","boolean","string"
     * @param obj         需要校验的数据
     * @return 校验结果
     */
    public static boolean resultDataTypeCheck(String desiredType, Object obj) {
        if (BASIC_DATA_TYPES.contains(desiredType)) {
            switch (desiredType) {
                case "integer":
                    if (obj instanceof BigDecimal) {
                        return true;
                    }
                    return obj instanceof Integer;
                case "double":
                    if (obj instanceof BigDecimal) {
                        return true;
                    }
                    return obj instanceof Double;
                case "string":
                    return obj instanceof String;
                case "boolean":
                    return obj instanceof Boolean;
            }
        }
        return false;
    }

    public static boolean resultDataTypeCheckForRuleScript(String desiredType, Object obj) {
        if (BASIC_DATA_TYPES.contains(desiredType)) {
            switch (desiredType) {
                case "string":
                    return obj instanceof String;
                default:
                    throw new IllegalArgumentException("no such desiredType for: " + desiredType);
            }
        }
        return false;
    }

    /**
     * 数据类型校验
     *
     * @param desiredType   支持："integer","double","boolean","string"
     * @param desiredResult 期望的结果
     * @param obj           需要校验的数据
     * @return 校验结果
     */
    public static Boolean resultDataCheck(String desiredType, String desiredResult, Object obj) {
        if (obj == null) {
            return false;
        }
        //默认按照字符串进行校验
        if (StringUtils.isBlank(desiredType)) {
            desiredType = "string";
        }
        if (BASIC_DATA_TYPES.contains(desiredType)) {
            switch (desiredType) {
                case "integer":
                    if (obj instanceof BigDecimal) {
                        BigDecimal result = (BigDecimal) obj;
                        if (result.intValue() == Integer.parseInt(desiredResult)) {
                            return true;
                        }
                    } else if (obj instanceof Integer) {
                        Integer result = (Integer) obj;
                        if (result.equals(Integer.valueOf(desiredResult))) {
                            return true;
                        }
                    }

                case "double":
                    if (obj instanceof BigDecimal) {
                        BigDecimal result = (BigDecimal) obj;
                        if (result.doubleValue() == Double.parseDouble(desiredResult)) {
                            return true;
                        }
                    } else if (obj instanceof Double) {
                        Double result = (Double) obj;
                        if (result.equals(Double.valueOf(desiredResult))) {
                            return true;
                        }
                    }
                case "string":
                    if (obj instanceof String) {
                        String result = (String) obj;
                        if (result.equals(desiredResult)) {
                            return true;
                        }
                    } else {
                        //兼容默认值
                        String result = obj.toString();
                        if (result.equals(desiredResult)) {
                            return true;
                        }
                    }
                case "boolean":
                    if (obj instanceof Boolean) {
                        Boolean result = (Boolean) obj;
                        if (result.equals(Boolean.valueOf(desiredResult))) {
                            return true;
                        }
                    }
            }
        }
        return false;
    }
}
